﻿using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using WeavingCommon;

namespace NotifyPropertyWeaverMsBuildTask
{
    [Export, PartCreationPolicy(CreationPolicy.Shared)]
    public class TypeProcessor
    {
        TypeNodeBuilder typeNodeBuilder;
        Logger logger;
        MsCoreReferenceFinder msCoreReferenceFinder;
        TypeEqualityFinder typeEqualityFinder;

    	[ImportingConstructor]
        public TypeProcessor(TypeNodeBuilder typeNodeBuilder, Logger logger,MsCoreReferenceFinder msCoreReferenceFinder, TypeEqualityFinder typeEqualityFinder)
        {
            this.typeNodeBuilder = typeNodeBuilder;
            this.logger = logger;
            this.msCoreReferenceFinder = msCoreReferenceFinder;
            this.typeEqualityFinder = typeEqualityFinder;
        }

        void Process(List<TypeNode> notifyNodes)
        {
            foreach (var node in notifyNodes)
            {
                if (node.EventInvoker == null)
                {
                    continue;
                }
                logger.LogMessage("\t" + node.TypeDefinition.FullName);

                foreach (var propertyData in node.PropertyDatas)
                {
                    if (AlreadyContainsNotification(propertyData.PropertyDefinition, node.EventInvoker.MethodReference.Name))
                    {
                        if (propertyData.NotificationAddedDirectly)
                        {
                            throw new WeavingException(string.Format("{0} Already has notification functionality.", propertyData.PropertyDefinition.GetName()));
                        }
                        logger.LogMessage(string.Format("\t{0} Already has notification functionality. Property will be ignored.", propertyData.PropertyDefinition.GetName()));
                        continue;
                    }
                    var propertyWeaver = new PropertyWeaver(msCoreReferenceFinder, logger, propertyData, node, typeEqualityFinder);
                    propertyWeaver.Execute();
                }
                Process(node.Nodes);
            }
        }

        public static bool AlreadyContainsNotification(PropertyDefinition propertyDefinition, string methodName)
        {
            var instructions = propertyDefinition.SetMethod.Body.Instructions;
            return instructions.Any(x =>
                                    ((x.OpCode.Code == Code.Call) || (x.OpCode.Code == Code.Callvirt)) &&
                                    x.Operand is MethodReference &&
                                    ((MethodReference)x.Operand).Name == methodName);
        }

        public void Execute()
        {
            Process(typeNodeBuilder.NotifyNodes);
        }
    }
}